package io.lvdaxian.mybatis19.builder.xml;

import io.lvdaxian.mybatis19.builder.BaseBuilder;
import io.lvdaxian.mybatis19.builder.BuilderException;
import io.lvdaxian.mybatis19.builder.MapperBuilderAssistant;
import io.lvdaxian.mybatis19.builder.ResultMapResolver;
import io.lvdaxian.mybatis19.cache.Cache;
import io.lvdaxian.mybatis19.io.Resources;
import io.lvdaxian.mybatis19.mapping.ResultFlag;
import io.lvdaxian.mybatis19.mapping.ResultMap;
import io.lvdaxian.mybatis19.mapping.ResultMapping;
import io.lvdaxian.mybatis19.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

/**
 * XML映射构建器
 *
 * @author lihh
 */
public class XMLMapperBuilder extends BaseBuilder {
  
  // element 元素
  private final Element element;
  // source 资源
  private final String resource;
  // 映射器构建助手
  private MapperBuilderAssistant builderAssistant;
  
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource) throws DocumentException {
    this(new SAXReader().read(inputStream), configuration, resource);
  }
  
  public XMLMapperBuilder(Document document, Configuration configuration, String resource) {
    super(configuration);
    this.element = document.getRootElement();
    this.resource = resource;
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
  }
  
  /**
   * 解析 xml 的核心方法
   *
   * @author lihh
   */
  public void parse() {
    if (configuration.isResourceLoaded(resource))
      return;
    
    // 开始解析 xml, 主要是拿到 namespace
    configurationElement(element);
    // 表示该 xml 解析过了
    configuration.addLoadedResource(resource);
    // 添加 mapper 此时的 这个currentNamespace 已经在上面 赋值过了
    configuration.addMapper(Resources.classForName(this.builderAssistant.getCurrentNamespace()));
  }
  
  /**
   * 解析 元素的核心方法
   * <p>
   * 配置mapper元素
   * <mapper namespace="org.mybatis.example.BlogMapper">
   * <select id="selectBlog" parameterType="int" resultType="Blog">
   * select * from Blog where id = #{id}
   * </select>
   * </mapper>
   *
   * @param element element 元素
   * @author lihh
   */
  private void configurationElement(Element element) {
    // 从 这里拿到 namespace
    String currentNamespace = element.attributeValue("namespace");
    if (currentNamespace == null || currentNamespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    
    this.builderAssistant.setCurrentNamespace(currentNamespace);
    
    // 这里 解析 cache 标签
    cacheElement(element.element("cache"));
    resultMapElements(element.elements("resultMap"));
    
    // todo 这里是解析 select的片段
    buildStatementFromContext(
        element.elements("select"),
        element.elements("update"),
        element.elements("delete"),
        element.elements("insert")
    );
  }
  
  /**
   * 这里 解析缓存标签
   *
   * @param context element 上下文
   * @author lihh
   */
  private void cacheElement(Element context) {
    if (context == null) return;
    
    // 基础配置信息
    // 解析 缓存实现类型
    String type = context.attributeValue("type", "PERPETUAL");
    // 解析 别名
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    // 缓存队列 FIFO 缓存清除策略
    String eviction = context.attributeValue("eviction", "LRU");
    // 拿到类
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    // 拿到 刷新时间
    Long flushInterval = Long.valueOf(context.attributeValue("flushInterval"));
    // size 是限制缓存大小
    Integer size = Integer.valueOf(context.attributeValue("size"));
    // 使用只读
    boolean readWrite = !Boolean.parseBoolean(context.attributeValue("readOnly", "false"));
    // 是否阻塞
    boolean blocking = !Boolean.parseBoolean(context.attributeValue("blocking", "false"));
    
    // 解析额外属性信息；<property name="cacheFile" value="/tmp/xxx-cache.tmp"/>
    List<Element> elements = context.elements();
    Properties props = new Properties();
    for (Element element : elements) {
      props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
    }
    // 构建缓存
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
  
  
  /**
   * resultMap 元素进行解析
   *
   * @param list 集合
   * @author lihh
   */
  private void resultMapElements(List<Element> list) {
    for (Element element : list) {
      try {
        resultMapElement(element, Collections.emptyList());
      } catch (Exception ignore) {
      }
    }
  }
  
  /**
   * 从上下文 解析出 statement 片段
   *
   * @param lists element 列表 这里将尝试多个 select 标签集
   * @author lihh
   */
  private void buildStatementFromContext(List<Element>... lists) {
    // 通过遍历的方式 一个一个 片段的进行处理
    for (List<Element> list : lists)
      for (Element element : list) {
        // 构建 片段的 statement
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, element);
        // 解析 statement
        statementParser.parseStatementNode();
      }
  }
  
  /**
   * <resultMap id="activityMap" type="cn.bugstack.mybatis.test.po.Activity">
   * <id column="id" property="id"/>
   * <result column="activity_id" property="activityId"/>
   * <result column="activity_name" property="activityName"/>
   * <result column="activity_desc" property="activityDesc"/>
   * <result column="create_time" property="createTime"/>
   * <result column="update_time" property="updateTime"/>
   * </resultMap>
   * <p>
   * 这里解析 resultMap 标签
   */
  private ResultMap resultMapElement(Element resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    // 从这里拿到 resultMap 的映射id
    String id = resultMapNode.attributeValue("id");
    // resultMap 的映射type（结果集要赋值的Java object）
    String type = resultMapNode.attributeValue("type");
    // 根据字符串 构建出的 Class
    Class<?> typeClass = resolveClass(type);
    
    // resultMappings 这是映射的结果集
    List<ResultMapping> resultMappings = new ArrayList<>();
    // 添加结果集
    resultMappings.addAll(additionalResultMappings);
    
    // 解析出的每个子元素
    List<Element> resultChildren = resultMapNode.elements();
    // 遍历出的子元素
    for (Element resultChild : resultChildren) {
      // 是id 还是 构造
      List<ResultFlag> flags = new ArrayList<>();
      // 解析出的是否为id
      if ("id".equals(resultChild.getName())) {
        // 设置 flag 标识
        flags.add(ResultFlag.ID);
      }
      // 构建 ResultMapping
      // 方法【buildResultMappingFromContext】 这里是构建子元素
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
    
    // 创建结果映射解析器
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, resultMappings);
    return resultMapResolver.resolve();
  }
  
  /**
   * <id column="id" property="id"/>
   * <result column="activity_id" property="activityId"/>
   *
   * @author lihh
   * 解析属性
   */
  private ResultMapping buildResultMappingFromContext(Element context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    // 拿到子元素的 property
    String property = context.attributeValue("property");
    // 拿到子元素的 column
    String column = context.attributeValue("column");
    return builderAssistant.buildResultMapping(resultType, property, column, flags);
  }
}
